package com.tools.db.ip;

import com.tools.common.container.MapEnum;
import com.tools.common.container.OwnRows;
import com.tools.common.exception.IORuntimeException;
import com.tools.common.io.FileKit;
import com.tools.common.io.ProfileKit;
import com.tools.common.object.Note;
import com.tools.common.object.string.StrKit;
import org.lionsoul.ip2region.xdb.Searcher;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * IP 操作的工具类
 * */
@Note("IP 操作的工具类")
public final class IPKit {

    private IPKit() {}

    @Note("本工程中默认的数据库文件的路径")
    private static String DB_FILE_PATH;

    @Note("本工具类默认的 Searcher 对象，是基于一定大小的缓冲区实现的 IP 查询（缓冲区具体多大这里决定不了）")
    private static volatile Searcher DEFAULT_SEARCHER;

    static {
        String dbFilePath = ProfileKit.getString("ip-ip2region-db-file-path");
        if(dbFilePath != null && !dbFilePath.isEmpty()) {
            DB_FILE_PATH = FileKit.getRealPath(dbFilePath);
        }
    }

    public static void setDbFilePath(String dbFilePath) {
        if(dbFilePath != null && !dbFilePath.isEmpty()) {
            DB_FILE_PATH = FileKit.getRealPath(dbFilePath);
        }
    }

    /* ********************************************************************************************
     *
     *          初始化 Searcher 对象的工具方法
     *
     * *********************************************************************************************
     * */


    @Note("【不支持并发，线程不安全】：" +
            "使用指定路径的 DB 文件完成基于文件查询 IP 地址的 Searcher 对象的初始化")
    public static Searcher onlyFileSearcher(String dbFilePath) {
        File file = new File(dbFilePath);
        if(!file.exists()) throw new IORuntimeException("找不到文件 【" + dbFilePath + "】，请确认路径是否存在");
        Searcher searcher;
        try {
            searcher = Searcher.newWithFileOnly(dbFilePath);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return searcher;
    }

    @Note("【不支持并发，线程不安全】：" +
            "使用指定路径的 DB 文件完成基于指定字节大小的缓存区域的 Searcher 对象的初始化")
    public static Searcher vectorIndexSearcher(String dbFilePath) {
        if(dbFilePath == null || dbFilePath.isEmpty()) throw new NullPointerException("IP 数据库文件为空");
        File file = new File(dbFilePath);
        if(!file.exists()) throw new IORuntimeException("找不到文件 【" + dbFilePath + "】，请确认路径是否存在");
        Searcher searcher;
        try {
            byte[] vectorIndex= Searcher.loadVectorIndexFromFile(dbFilePath);
            searcher = Searcher.newWithVectorIndex(dbFilePath, vectorIndex);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return searcher;
    }


    @Note("【支持并发，可设置为单例】：" +
            "使用指定路径的 DB 文件完成加载所有 IP 数据到缓存区域，然后基于此数据的 Searcher 对象的初始化")
    public static Searcher loadAllDataSearcher(String dbFilePath) {
        if(dbFilePath == null || dbFilePath.isEmpty()) throw new NullPointerException("IP 数据库文件为空");
        File file = new File(dbFilePath);
        if(!file.exists()) throw new IORuntimeException("找不到文件 【" + dbFilePath + "】，请确认路径是否存在");
        Searcher searcher;
        try {
            byte[] allDataBuff= Searcher.loadContentFromFile(dbFilePath);
            searcher = Searcher.newWithBuffer(allDataBuff);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return searcher;
    }


    /* ********************************************************************************************
     *
     *          查询单个 IP 的信息、字符串和数字的 IP 地址转换等等的工具方法
     *
     * *********************************************************************************************
     * */

    @Note("把字符串 IPv4 地址转化为纯数字")
    public static long toNumberIP(String ip) {
        String[] array = StrKit.split(ip, ".");
        return (Long.parseLong(array[0]) << 24) + (Long.parseLong(array[1]) << 16) + (Long.parseLong(array[2]) << 8) + Long.parseLong(array[3]);
    }

    @Note("把数字 IP 转化为字符串的 IPv4 地址，" +
            "第一段右移 24 位，第二段将高 8 位置 0，然后右移 16 位" +
            "第三段将高 16 位置 0，然后右移 8 位， 第四段将高 24 位置 0")
    public static String toStringIp(long ip) {
        return (ip >>> 24) +
                "." + ((ip & 0x00FFFFFF) >>> 16) +
                "." + ((ip & 0x0000FFFF) >>> 8) +
                "." + (ip & 0x000000FF);
    }

    @Note("获取 IP 归属信息的字符串，格式为 【国家|区域|省份|城市|运营商】")
    public static String findIPToString(Searcher searcher, String ip) {
        if(ip == null || ip.isEmpty()) return "";
        try {
            return searcher.search(ip);
        } catch (Exception e) {
            throw new IORuntimeException(e);
        }
    }

    @Note("获取 IP 归属信息的字符串，格式为 【国家|区域|省份|城市|运营商】")
    public static String findIPToString(Searcher searcher, long ip) {
        try {
            return searcher.search(ip);
        } catch (Exception e) {
            throw new IORuntimeException(e);
        }
    }

    @Note("获取 IP 归属信息的封装实体 IPBean 对象")
    public static IPBean findIPToBean(Searcher searcher, String ip) {
        String result = findIPToString(searcher, ip);
        IPBean ipBean = new IPBean();
        ipBean.setId(toNumberIP(ip));
        ipBean.setIp(ip);
        private_fillInIPData(ipBean, result);
        return ipBean;
    }

    @Note("获取 IP 归属信息的封装实体 IPDataBase 对象")
    public static IPBean findIPToBean(Searcher searcher, long ip) {
        String result = findIPToString(searcher, ip);
        IPBean ipBean = new IPBean();
        ipBean.setId(ip);
        ipBean.setIp(toStringIp(ip));
        private_fillInIPData(ipBean, result);
        return ipBean;
    }




    /* ********************************************************************************************
     *
     *          查询多个 IP 的信息封装为容器返回的工具方法
     *
     * *********************************************************************************************
     * */

    @Note("遍历搜索 IP 的信息，并将其封装为实体存入 ArrayList 集合里返回")
    public static List<IPBean> findMoreAsList(Searcher searcher, String... ips) {
        if(ips == null || ips.length == 0) return new ArrayList<>();
        List<IPBean> result = new ArrayList<>(ips.length);
        for (String ip : ips) {
            result.add(findIPToBean(searcher, ip));
        }
        return result;
    }

    @Note("遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<String, IPBean> findMoreAsMap(Searcher searcher, String... ips) {
        if(ips == null || ips.length == 0) return new HashMap<>();
        Map<String, IPBean> result = MapEnum.HASH.newMap(ips.length);
        for(String ip : ips) {
            IPBean data = findIPToBean(searcher, ip);
            result.put(ip, data);
        }
        return result;
    }

    @Note("遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<String, IPBean> findMoreAsMapByStringIP(Searcher searcher, Collection<String> ips) {
        if(ips == null || ips.isEmpty()) return new HashMap<>();
        Map<String, IPBean> result = new HashMap<>(MapEnum.calcInitSize(ips.size()));
        for(String ip : ips) {
            IPBean data = findIPToBean(searcher, ip);
            result.put(ip, data);
        }
        return result;
    }


    @Note("遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<String, IPBean> findMoreAsMapByStringIP(Searcher searcher, OwnRows<String> ips) {
        if(ips != null && !ips.isEmpty()) {
            Map<String, IPBean> result = new HashMap<>(MapEnum.calcInitSize(ips.size()));
            for(String ip : ips) {
                result.put(ip, findIPToBean(searcher, ip));
            }
            return result;
        }
        return new HashMap<>();
    }


    @Note("遍历搜索 IP 的信息，并将其封装为实体存入 ArrayList 集合里返回")
    public static List<IPBean> findMoreAsList(Searcher searcher, long... ips) {
        if(ips == null || ips.length == 0) return new ArrayList<>();
        List<IPBean> result = new ArrayList<>(ips.length);
        for (long ip : ips) {
            result.add(findIPToBean(searcher, ip));
        }
        return result;
    }


    @Note("遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<Long, IPBean> findMoreAsMap(Searcher searcher, long... ips) {
        if(ips == null || ips.length == 0) return new HashMap<>(0);
        Map<Long, IPBean> result = new HashMap<>(MapEnum.calcInitSize(ips.length));
        for(long ip : ips) {
            IPBean data = findIPToBean(searcher, ip);
            result.put(ip, data);
        }
        return result;
    }


    @Note("遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<Long, IPBean> findMoreAsMapByNumberIP(Searcher searcher, Collection<Long> ips) {
        if(ips == null || ips.isEmpty()) return new HashMap<>();
        Map<Long, IPBean> result = new HashMap<>(MapEnum.calcInitSize(ips.size()));
        int index = 0;
        for(Long ip : ips) {
            if(ip == null) throw new NullPointerException("第 " + index + " 个 IP 为 null");
            result.put(ip, findIPToBean(searcher, ip));
            index++;
        }
        return result;
    }


    @Note("遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<Long, IPBean> findMoreAsMapByNumberIP(Searcher searcher, OwnRows<Long> ips) {
        if(ips != null && !ips.isEmpty()) {
            int index = 0;
            Map<Long, IPBean> result = new HashMap<>(MapEnum.calcInitSize(ips.size()));
            for(Long ip : ips) {
                if(ip == null) throw new NullPointerException("第 " + index + " 个 IP 为 null");
                result.put(ip, findIPToBean(searcher, ip));
                index++;
            }
            return result;
        }
        return new HashMap<>();
    }

    /* ********************************************************************************************
     *
     *          使用默认 Search 对象查询的方法
     *
     * *********************************************************************************************
     * */

    @Note("使用默认的 Searcher 对象获取 IP 归属信息的字符串，格式为 【国家|区域|省份|城市|运营商】")
    public static String findIPToString(String ip) {
        private_lazyInitDefaultSearch();
        return findIPToString(DEFAULT_SEARCHER, ip);
    }

    @Note("获取 IP 归属信息的字符串，格式为 【国家|区域|省份|城市|运营商】")
    public static String findIPToString(long ip) {
        private_lazyInitDefaultSearch();
        return findIPToString(DEFAULT_SEARCHER, ip);
    }


    @Note("使用默认的 Searcher 对象获取 IP 归属信息的封装实体 IPBean 对象")
    public static IPBean findIPToBean(String ip) {
        private_lazyInitDefaultSearch();
        return findIPToBean(DEFAULT_SEARCHER, ip);
    }


    @Note("使用默认的 Searcher 对象获取 IP 归属信息的封装实体 IPBean 对象")
    public static IPBean findIPToBean(long ip) {
        private_lazyInitDefaultSearch();
        return findIPToBean(DEFAULT_SEARCHER, ip);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体存入 ArrayList 集合里返回")
    public static List<IPBean> findMoreAsList(String... ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsList(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体存入 ArrayList 集合里返回")
    public static List<IPBean> findMoreAsList(long... ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsList(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<String, IPBean> findMoreAsMap(String... ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsMap(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<Long, IPBean> findMoreAsMap(long... ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsMap(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<String, IPBean> findMoreAsMapByStringIP(Collection<String> ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsMapByStringIP(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<Long, IPBean> findMoreAsMapByNumberIP(Collection<Long> ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsMapByNumberIP(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<String, IPBean> findMoreAsMapByStringIP(OwnRows<String> ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsMapByStringIP(DEFAULT_SEARCHER, ips);
    }

    @Note("使用默认的 Searcher 对象遍历搜索 IP 的信息，并将其封装为实体，key 为 IP，value 为 IP 信息的实体，存入 HashMap 集合里返回")
    public static Map<Long, IPBean> findMoreAsMapByNumberIP(OwnRows<Long> ips) {
        private_lazyInitDefaultSearch();
        return findMoreAsMapByNumberIP(DEFAULT_SEARCHER, ips);
    }

    /* ********************************************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************************************
     * */

    @Note("私有逻辑：如果字符串数据为 0 或者 null ，则返回 null")
    private static String private_zeroDefaultValue(String data) {
        return (data == null || "0".equals(data)) ? null : data;
    }

    @Note("私有逻辑：把查询出来的结果数据封装到 IPBean 实体里")
    private static void private_fillInIPData(IPBean ipBean, String result) {
        if(result == null || result.isEmpty()) return;
        String[] resultArray = result.split("\\|");
        ipBean.setCountry(private_zeroDefaultValue(resultArray[0]));
        ipBean.setProvince(private_zeroDefaultValue(resultArray[2]));
        ipBean.setCity(private_zeroDefaultValue(resultArray[3]));
        ipBean.setRegion(private_zeroDefaultValue(resultArray[1]));
        ipBean.setIsp(private_zeroDefaultValue(resultArray[4]));
    }

    @Note("根据 separator 解析字符串，将解析后的结果封装到 IPBean 实例里")
    static void default_fillInIPData(IPBean ipBean, String result, String separator) {
        String[] resultArray = result.split(separator);
        ipBean.setCountry(private_zeroDefaultValue(resultArray[0]));
        ipBean.setProvince(private_zeroDefaultValue(resultArray[2]));
        ipBean.setCity(private_zeroDefaultValue(resultArray[3]));
        ipBean.setRegion(private_zeroDefaultValue(resultArray[1]));
        ipBean.setIsp(private_zeroDefaultValue(resultArray[4]));
    }


    @Note("懒加载默认的 Searcher 查询对象")
    private static void private_lazyInitDefaultSearch() {
        if(DEFAULT_SEARCHER != null) return;
        synchronized (IPKit.class) {
            if(DEFAULT_SEARCHER != null) return;
            DEFAULT_SEARCHER = loadAllDataSearcher(DB_FILE_PATH);
        }
    }
}
